<?php

namespace Drupal\jsonapi_cross_bundles\ResourceType;

use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\jsonapi\ResourceType\ResourceType;
use Drupal\jsonapi\ResourceType\ResourceTypeRepository;
use Drupal\jsonapi_extras\ResourceType\ConfigurableResourceTypeRepository;
use Drupal\jsonapi_cross_bundles\ResourceType\CrossBundlesResourceType;
use Drupal\jsonapi_cross_bundles\Enitty\OverrideJsonapiResourceConfig as JsonapiResourceConfig;

/**
 * Provides a decorated resource type repository for cross bundle collections.
 */
class CrossBundleResourceTypeRepository extends ConfigurableResourceTypeRepository {

  /**
   * {@inheritdoc}
   */
  public function all() {
    $cached = $this->staticCache->get('jsonapi.resource_types', FALSE);
    if ($cached === FALSE) {
      $initial_resource_types = parent::all();
      $grouped_by_entity_type_id = array_reduce($initial_resource_types, function ($grouped, ResourceType $resource_type) {
        return array_merge_recursive($grouped, [
          $resource_type->getEntityTypeId() => [$resource_type],
        ]);
      }, []);
      $cross_bundle_resource_types = [];
      $entity_types = $this->entityTypeManager->getDefinitions();
      foreach ($grouped_by_entity_type_id as $entity_type_id => $resource_types) {
        if (count($resource_types) < 2) {
          continue;
        }
        $entity_type = $entity_types[$entity_type_id];
        if (!$entity_type->getKey('bundle')) {
          continue;
        }
        // If none of the bundle-specific resource types is locatable, the
        // cross-bundle resource type should not be locatable.
        $at_least_one_is_locatable = array_reduce($resource_types, function ($at_least_one_is_locatable, ResourceType $resource_type) {
          return $at_least_one_is_locatable ?: $resource_type->isLocatable();
        }, FALSE);
        $field_mapping_superset = static::getFieldMappingSuperset(array_map(function (ResourceType $resource_type) use ($entity_type) {
          return static::getFieldMapping(
            $this->getAllFieldNames($entity_type, $resource_type->getBundle()),
            $entity_type,
            $resource_type->getBundle()
          );
        }, $resource_types));
        $cross_bundle_resource_type = new CrossBundlesResourceType(
          $entity_type_id,
          $entity_type_id,
          $entity_type->getClass(),
          $entity_type->isInternal(),
          $at_least_one_is_locatable,
          FALSE,
          FALSE,
          $field_mapping_superset
        );
        $cross_bundle_resource_type->setBundleResourceTypes($resource_types);
        $cross_bundle_resource_type->setRelatableResourceTypes(static::getRelatableResourceTypesSuperset($resource_types, $field_mapping_superset));
        
        // 补充jsonapi_extra需要的属性
        $resource_config_id = static::buildResourceConfigId(
          $entity_type->id(),
          $entity_type->id()
        );
        $resource_config = $this->getResourceConfig($resource_config_id);
        $cross_bundle_resource_type->setJsonapiResourceConfig($resource_config);
        $cross_bundle_resource_type->setEnhancerManager($this->enhancerManager);
        $cross_bundle_resource_type->setConfigFactory($this->configFactory);

        $cross_bundle_resource_types[] = $cross_bundle_resource_type;
      }
      $decorated_resource_types = array_merge($initial_resource_types, $cross_bundle_resource_types);
      $this->staticCache->set('jsonapi.resource_types', $decorated_resource_types, Cache::PERMANENT, ['jsonapi_resource_types']);
    }
    return $cached ? $cached->data : $decorated_resource_types;
  }

  public function getResourceConfigs() {
    if (!$this->resourceConfigs) {
      $this->resourceConfigs = $this->entityTypeManager
        ->getStorage('jsonapi_resource_config')
        ->loadMultiple();
    }
    return $this->resourceConfigs;
  }

  /**
   * Computes a superset of field mappings.
   *
   * @param array $field_mappings
   *   An array of field mapping arrays for each of the bundle-specific
   *   resource types that a cross-bundle resource type will aggregate.
   *
   * @return array
   *   The field mapping superset.
   *
   * @see \Drupal\jsonapi\ResourceType\ResourceTypeRepository::getFieldMapping()
   */
  public static function getFieldMappingSuperset(array $field_mappings) {
    $superset = [];
    foreach ($field_mappings as $field_mapping) {
      // Merge the different field mappings into a single superset of all
      // possible field mappings for a single entity type.
      $undefined_field_names = [];
      $superset = array_reduce(array_keys($field_mapping), function ($superset, $internal_field_name) use ($field_mapping, &$undefined_field_names) {
        if (in_array($internal_field_name, $undefined_field_names)) {
          return $superset;
        }
        // Check whether the field name already has a mapping.
        if (isset($superset[$internal_field_name])) {
          // Check to see if the field name is mapped or not. A boolean means
          // the field is not mapped and is either enabled or disabled.
          if (is_bool($superset[$internal_field_name])) {
            // The previous mapping is just enabled/disabled. If the new mapping
            // is a string, use it. If the it is enabled, use it. If it is
            // disabled, use the previous field mapping in case it is enabled.
            $superset[$internal_field_name] = $field_mapping[$internal_field_name] ?: $superset[$internal_field_name];
          } elseif ($superset[$internal_field_name] !== $field_mapping[$internal_field_name]) {
            // There are must be more than one possible field mapping, so leave
            // that behavior undefined.
            $undefined_field_names[] = $internal_field_name;
            unset($superset[$internal_field_name]);
          }
        } else {
          // This is a previous unseen mapping, so use the new mapping.
          $superset[$internal_field_name] = $field_mapping[$internal_field_name];
        }
        return $superset;
      }, $superset);
    }
    return $superset;
  }

  /**
   * Computes the superset of relatable resource types.
   *
   * @param array $resource_types
   *   An array of resource types to be aggregated.
   *
   * @param $field_mapping
   *   The cross-bundle resource type field mapping.
   *
   * @return array
   *   A superset of relatable resource types, keyed by public field name.
   *
   * @see \Drupal\jsonapi\ResourceType\ResourceTypeRepository::getRelatableResourceTypesFromFieldDefinition()
   */
  public static function getRelatableResourceTypesSuperset(array $resource_types, $field_mapping) {
    $relatable_resource_types = array_reduce(array_map(function (ResourceType $resource_type) {
      return $resource_type->getRelatableResourceTypes();
    }, $resource_types), 'array_merge', []);
    array_walk($field_mapping, function (&$public_field_name, $internal_field_name) {
      if ($public_field_name === TRUE) {
        $public_field_name =  $internal_field_name;
      }
    });
    return array_intersect_key($relatable_resource_types, array_flip(array_keys(array_filter($field_mapping))));
  }

}
